home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2008 January / Mac_easy_01_08.iso / Software / iPod & iTunes / iPodBackup.dmg / iPodBackup.app / Contents / Resources / script < prev    next >
Encoding:
Text File  |  2005-07-17  |  23.3 KB  |  607 lines

  1. #!/bin/sh
  2.  
  3. # Copyright 2004, 2005 Aaron Madlon-Kay
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  
  19.  
  20. ### Variable declaration
  21. APPNAME=iPodBackup
  22. VERSION=1.5.4
  23. DATE=2005-07-18
  24. EMAIL="ipodbackup@mac.com"
  25. HOMEPAGE="http://www.amake.us"
  26. #PLATYPUS_ARGS=-o ProgressBar -D
  27. #PACKAGE_STYLE=dmg
  28. THISAPP="$1/Contents/Resources"
  29. CD="$THISAPP/CocoaDialog.app/Contents/MacOS/CocoaDialog"
  30. LOGFILE="$HOME/Library/Logs/$APPNAME.log"
  31. EXCEPTIONS="$HOME/Library/Preferences/$APPNAME Exceptions.txt"
  32. USERPREFS="$HOME/Library/Preferences/$APPNAME Preferences.txt"
  33.  
  34. ### Detects system language, chooses appropriate strings file
  35. ### See /System/Library/PrivateFrameworks/IntlPreferences.framework for names
  36. get_language() {
  37.     LANGUAGES=$(defaults read "Apple Global Domain" AppleLanguages | head -n 1 | sed -e 's/[,()"]//g')
  38.     # Try each language successively until a strings file is found
  39.     for LANG in $LANGUAGES; do
  40.         if (echo $LANG | grep -qi "^en"); then
  41.             STRINGS_FILE="$THISAPP/Localizable.strings"
  42.         else
  43.             STRINGS_FILE="$THISAPP/$LANG.lproj/Localizable.strings"
  44.         fi
  45.         [[ -r "$STRINGS_FILE" ]] && break
  46.     done
  47.     # If the appropriate localized string file isn't found, default to English
  48.     [[ ! -r "$STRINGS_FILE" ]] && STRINGS_FILE="$THISAPP/Localizable.strings"
  49.     [[ -r "$STRINGS_FILE" ]] && . "$STRINGS_FILE"
  50.     echo "Using $STRINGS_LANGUAGE strings version $STRINGS_VERSION by $STRINGS_AUTHOR" >> "$LOGFILE"
  51. }
  52.  
  53. ### Reads in all the preferences.
  54. load_preferences() {
  55.     # If preferences or exceptions are missing, copy them in.
  56.     [[ ! -f "$EXCEPTIONS" ]] && cp "$THISAPP/exceptions.txt" "$EXCEPTIONS"
  57.     [[ ! -f "$USERPREFS" ]] && cp "$THISAPP/preferences.txt" "$USERPREFS"
  58.     COMPACT=$(read_pref compact)
  59.     CHIME=$(read_pref chime)
  60.     PROMPT=$(read_pref prompt)
  61.     NO_IPOD_WARNING=$(read_pref no_ipod_warning)
  62.     DANGER_LEVEL=$(read_pref danger_level)
  63.     [[ -z "$DANGER_LEVEL" ]] && DANGER_LEVEL=10000000
  64.     TOTAL_DATA_SIZE=$(read_pref total_data_size)
  65.     TOTAL_RUNS=$(read_pref total_runs)
  66.     DO_NOT_NAG=$(read_pref do_not_nag)
  67.     PREFERRED_IPOD=$(read_pref preferred_ipod)
  68.     CHECK_FOR_UPDATES=$(read_pref check_for_updates)
  69.     OS_VERSION=$(read_pref manual_os_version)
  70.     OWNERSHIP_WARNING=$(read_pref ownership_warning)
  71.     [[ -z "$OS_VERSION" ]] && OS_VERSION=$(uname -r | awk -F. '{print $1}')
  72.     if (( "$OS_VERSION" >= "8" )); then
  73.         RSYNC="/usr/bin/rsync"
  74.         ARGS="-avE"
  75.     else
  76.         RSYNC="$THISAPP/rsync"
  77.         ARGS="-av --eahfs"
  78.     fi
  79.     IGNORE_ERRORS=$(read_pref ignore_errors)
  80.     [[ "$IGNORE_ERRORS" = "true" ]] && ARGS="$ARGS --ignore-errors"
  81. }
  82.  
  83. ### Reads a specified preference, making sure it is unique
  84. read_pref() {
  85.     if [[ "$#" != "1" ]]; then
  86.         echo "Error attempting to read pref \"$1\": Bad arguments" >> "$LOGFILE"
  87.         exit 1
  88.     fi
  89.     if [[ ! -r "$USERPREFS" ]]; then
  90.         local PREFSFILE="$THISAPP/preferences.txt"
  91.     else
  92.         local PREFSFILE="$USERPREFS"
  93.     fi
  94.     local READPREF=$(awk -F= "\$1==\"$1\" {print \$2}" "$PREFSFILE")
  95.     if (( "$(echo "$READPREF" | awk 'END {print NR}')" > "1" )); then
  96.         echo "Error: Preferences file is invalid:  Multiple instances of \"$1\"" >> "$LOGFILE"
  97.         exit 1
  98.     fi
  99.     echo "$READPREF"
  100. }
  101.  
  102. ### Adds setting to prefs file while ensuring it is unique
  103. set_pref() {
  104.     if [[ "$#" != "2" ]]; then
  105.         echo "Error attempting to set pref \"$1\": Bad arguments" >> "$LOGFILE"
  106.         exit 1
  107.     fi
  108.     if [[ "$1" = "delete" ]]; then
  109.         local PREF="$2"
  110.     else
  111.         local PREF="$1"
  112.         local VALUE="$2"
  113.     fi
  114.     awk -F= "\$1!=\"$PREF\" {print \$0}" "$USERPREFS" > "$USERPREFS-$$"
  115.     mv "$USERPREFS-$$" "$USERPREFS"
  116.     if [[ ! -z "$VALUE" ]]; then
  117.         echo "$1=$2" >> "$USERPREFS"
  118.         echo "Set pref \"$1=$2\"" >> "$LOGFILE"
  119.     fi
  120. }
  121.  
  122. ### Make sure user has write permissions on specified item
  123. test_permissions() {
  124.     if [[ ! -w "$1" ]]; then
  125.         # Bad permissions error
  126.         "$CD" msgbox --title "$STRING1a" --text "$STRING1b $1" --informative-text "$STRING1c" --style critical --button1 "$STRING1d" > /dev/null
  127.         echo "Bad permissions on \"$1\"" >> "$LOGFILE"
  128.         exit 1
  129.     fi
  130. }
  131.  
  132. ### Deals with arguments passed to this script (drag-and-dropped items)
  133. handle_arguments() {
  134.     if (( "$#" > "1" )); then
  135.         echo "Editing exceptions via drag-and-drop" >> "$LOGFILE"
  136.         echo >> "$EXCEPTIONS"
  137.         until [[ -z "$2" ]]; do
  138.             edit_exceptions "$2"
  139.             shift
  140.         done
  141.         sed -e '/^$/d' "$EXCEPTIONS" > "$EXCEPTIONS-$$"
  142.         mv "$EXCEPTIONS-$$" "$EXCEPTIONS"
  143.         for INDEX in 0 1; do
  144.             # ${RESULT[0]} is additions, ${RESULT[1]} is removals
  145.             if [[ -z "${RESULT[$INDEX]}" ]]; then
  146.                 RESULT[$INDEX]="$STRING2f"    # "none"
  147.             else
  148.                 RESULT[$INDEX]=$(echo "${RESULT[$INDEX]}" | sed -e "s/$STRING2g \$//")    # Remove trailing punctuation
  149.             fi
  150.         done
  151.         echo "Done editing exceptions" >> "$LOGFILE"
  152.         # Dialog 2: Editing report
  153.         "$CD" msgbox --title "$STRING2a" --text "$STRING2b" --informative-text "$STRING2c ${RESULT[0]}$STRING2h  $STRING2d ${RESULT[1]}$STRING2h" --button1 "$STRING2e" > /dev/null
  154.         PROMPT=true
  155.     fi
  156. }
  157.  
  158. ### Add or remove drag-and-dropped items from the exceptions file
  159. edit_exceptions() {
  160.     if (echo "$1" | grep -vq "^$HOME"); then
  161.         # Dialog 3: Not in home folder error
  162.         if [[ "$SKIP" != "2" ]]; then
  163.             SKIP=$("$CD" msgbox --title "$STRING3a" --text "$STRING3b $(basename "$1")" --informative-text "$STRING3c" --style warning --button1 "$STRING3d" --button2 "$STRING3e")
  164.         fi
  165.     else
  166.         FILE=${1:${#HOME}}        # Strips $HOME from beginning of path ($1)
  167.         [[ -d "$1" ]] && FILE=$FILE/
  168.         if ([[ -e "$1" ]] && (awk "\$0==\"- $FILE\" {exit 1}" "$EXCEPTIONS")); then
  169.             echo "Adding $1" >> "$LOGFILE"
  170.             RESULT[0]=$(echo "${RESULT[0]}$(basename "$1")$STRING2g ")
  171.             echo -e "- $FILE" >> "$EXCEPTIONS"
  172.         elif [[ -e "$1" ]]; then
  173.             echo "Removing $1" >> "$LOGFILE"
  174.             RESULT[1]=$(echo "${RESULT[1]}$(basename "$1")$STRING2g ")
  175.             awk "{if (\$0!=\"- $FILE\") print \$0}" "$EXCEPTIONS" > "$EXCEPTIONS-$$"
  176.             mv "$EXCEPTIONS-$$" "$EXCEPTIONS"
  177.         else
  178.             # Dialog 4: Not a valid file error
  179.             "$CD" msgbox --title "$STRING4a" --text "$STRING4b $(basename "$1")" --informative-text "$STRING4c" --style critical --button1 "$STRING4d" > /dev/null
  180.         fi
  181.     fi
  182. }
  183.     
  184. ### Test to see if an iPod is connected.  If not, quit.
  185. test_for_ipod() {
  186.     IPOD=$(find /Volumes -name "iPod_Control" -maxdepth 2)
  187.     if (( "$(echo "$IPOD" | awk 'END {print NR}')" > "1" )); then
  188.         # If the preferred iPod isn't found among current iPods, ask user
  189.         if (echo "$IPOD" | awk "\$0==\"$PREFERRED_IPOD\" {exit 1}"); then
  190.             # Dialog 5: Multiple iPods warning
  191.             echo -n "Multiple iPods found ... " >> "$LOGFILE"
  192.             ASK=($("$CD" dropdown --title "$STRING5a" --text "$STRING5b" --items $(echo "$IPOD" | awk -F/ '$2=="Volumes" {print $3}' | tr " " "_" | tr "\n" " ") --button1 "$STRING5c" --button2 "$STRING5d" --button3 "$STRING5e" --no-newline --width 400))
  193.             case "${ASK[0]}" in
  194.                 "2")    echo "User cancelled the iPod selection" >> "$LOGFILE"
  195.                         exit 1;;
  196.                 "1")    IPOD=$(echo "$IPOD" | sed -n "$((${ASK[1]} + 1))p")
  197.                         echo "User chose \"$IPOD\"" >> "$LOGFILE";;
  198.                 "3")    IPOD=$(echo "$IPOD" | sed -n "$((${ASK[1]} + 1))p")
  199.                         echo "User prefers \"$IPOD\"" >> "$LOGFILE"
  200.                         set_pref preferred_ipod "$IPOD";;
  201.             esac
  202.         else
  203.             IPOD="$PREFERRED_IPOD"
  204.         fi
  205.     fi
  206.     if [[ ! -d "$IPOD" ]]; then
  207.         echo "No iPod was found!" >> "$LOGFILE"
  208.         if [[ "$NO_IPOD_WARNING" != "false" ]]; then
  209.             # Dialog 6: No iPod was found
  210.             ASK=$("$CD" msgbox --title "$STRING6a" --text "$STRING6b" --style critical --button1 "$STRING6c" --button2 "$STRING6d" --no-newline)
  211.             case "$ASK" in
  212.                 "1")    ;;
  213.                 "2")    set_pref no_ipod_warning false
  214.             esac
  215.         fi
  216.         exit 1
  217.     fi
  218.     IPODNAME=$(echo "$IPOD" | awk -F/ '{print $3}')
  219.     DESTINATION="$IPOD"/../Users/"$USER"
  220.     DISKIMAGE="$DESTINATION".sparseimage
  221. }
  222.  
  223. ### Set up backup and prompt the user to set preferences.
  224. first_time_setup() {
  225.     echo "No previous backup detected" >> "$LOGFILE"
  226.     test_permissions "$IPOD"/../
  227.     mkdir -m 777 "$IPOD"/../Users 2> /dev/null
  228.     test_permissions "$IPOD"/../Users
  229.     touch "$IPOD"/../Users/.localized
  230.     # Dialog 7: Welcome
  231.     ASK=$("$CD" msgbox --title "$STRING7a" --text "$STRING7b" --informative-text "$STRING7c" --button1 "$STRING7d" --button2 "$STRING7e" --button3 "$STRING7f" --no-newline)
  232.     case "$ASK" in
  233.         "1")    ;;
  234.         "2")    echo "User cancelled the setup" >> "$LOGFILE"
  235.                 exit 0;;
  236.         "3")    open "$HOMEPAGE/software/$(echo $APPNAME | tr "[:upper:]" "[:lower:]")";;
  237.     esac
  238.     # Dialog 8: Logging
  239.     ASK=$("$CD" msgbox --title "$STRING8a" --text "$STRING8b" --informative-text "$STRING8c" --button1 "$STRING8d" --button2 "$STRING8e" --no-newline)
  240.     case "$ASK" in
  241.         "1")    ;;
  242.         "2")    open "$LOGFILE";;
  243.     esac
  244.     if (( "$OS_VERSION" <= "6" )); then
  245.         # Sparse disk images are not supported on OS X 10.2 and earlier
  246.         create_destination 3
  247.     else
  248.         # Dialog 9: Backup type
  249.         ASK=$("$CD" msgbox --title "$STRING9a" --text "$STRING9b" --informative-text "$STRING9c" --button1 "$STRING9d" --button2 "$STRING9e" --button3 "$STRING9f" --no-newline)
  250.         create_destination $ASK
  251.     fi
  252.     # Dialog 10: "Customize or Default"
  253.     ASK=$("$CD" msgbox --title "$STRING10a" --text "$STRING10b" --informative-text "$STRING10c" --button1 "$STRING10d" --button2 "$STRING10e" --button3 "$STRING10f" --no-newline)
  254.     case "$ASK" in
  255.         "1")    customize_settings;;
  256.         "2")    cp "$THISAPP"/preferences.txt "$USERPREFS";;
  257.         "3")    ;;
  258.     esac
  259.     # Dialog 11: Disk space warning
  260.     "$CD" msgbox --style warning --title "$STRING11a" --text "$STRING11b" --informative-text "$STRING11c" --button1 "$STRING11d" > /dev/null
  261. }
  262.  
  263. ### Set additional preferences
  264. customize_settings() {
  265.     echo "Customizing preferences" >> "$LOGFILE"
  266.     # Dialog 12: Exceptions
  267.     ASK=$("$CD" msgbox --title "$STRING12a" --text "$STRING12b" --informative-text "$STRING12c" --button1 "$STRING12d" --button2 "$STRING12e" --no-newline)
  268.     case "$ASK" in
  269.         "1")    ;;
  270.         "2")    open "$EXCEPTIONS"
  271.                 # Dialog 13: Waiting for rule edit
  272.                 "$CD" msgbox --title "$STRING13a" --text "$STRING13b" --button1 "$STRING13c" > /dev/null;;
  273.     esac
  274.     if [[ -f "$DISKIMAGE" ]]; then
  275.         # Dialog 14: Compacting
  276.         ASK=$("$CD" msgbox --title "$STRING14a" --text "$STRING14b" --informative-text "$STRING14c" --button1 "$STRING14d" --button2 "$STRING14e" --no-newline)
  277.         case "$ASK" in
  278.             "1")    set_pref compact true;;
  279.             "2")    set_pref compact false;;
  280.         esac
  281.     fi
  282.     # Dialog 15: Chime
  283.     ASK=$("$CD" msgbox --title "$STRING15a" --text "$STRING15b" --informative-text "$STRING15c" --button1 "$STRING15d" --button2 "$STRING15e" --no-newline)
  284.     case "$ASK" in
  285.         "1")    # Dialog 16: Chime select
  286.                 set_pref chime "$("$CD" fileselect --title "$STRING16a" --text "$STRING16b" --with-extensions .snd .wav .aiff .aif --with-directory /System/Library/Sounds --no-newline)";;
  287.         "2")    set_pref chime none;;
  288.     esac
  289.     # Dialog 17: Prompt dialog
  290.     ASK=$("$CD" msgbox --title "$STRING17a" --text "$STRING17b" --informative-text "$STRING17c" --button1 "$STRING17d" --button2 "$STRING17e" --no-newline)
  291.     case "$ASK" in
  292.         "1")    set_pref prompt false;;
  293.         "2")    set_pref prompt true;;
  294.     esac
  295. }
  296.  
  297. ### Displays prompt, if enabled.  User is given option of editing rules.
  298. preflight_prompt() {
  299.     if [[ "$PROMPT" = "true" ]]; then
  300.         echo "Displaying preflight prompt" >> "$LOGFILE"
  301.         # Dialog 18: Preflight prompt
  302.         ASK=$("$CD" msgbox --title "$STRING18a" --text "$STRING18b" --button1 "$STRING18c" --button2 "$STRING18d" --button3 "$STRING18e" --no-newline)
  303.         case "$ASK" in
  304.             "1")    ;;
  305.             "2")    echo "User cancelled the backup" >> "$LOGFILE"
  306.                     exit 0;;
  307.             "3")    open "$EXCEPTIONS"
  308.                     # Dialog 13: Waiting for rule edit
  309.                     "$CD" msgbox --title "$STRING13a" --text "$STRING13b" --button1 "$STRING13c" > /dev/null;;
  310.         esac
  311.     fi
  312. }
  313.  
  314. ### Creates the folder or disk image, then double-checks it.
  315. create_destination() {
  316.     case "$1" in
  317.         "1")    echo "Selection:  encrypted disk image" >> "$LOGFILE"
  318.                 ENCRYPTION="-encryption";;
  319.         "2")    echo "Selection:  non-encrypted disk image" >> "$LOGFILE"
  320.                 ENCRYPTION="";;
  321.         "3")    echo "Selection:  folder" >> "$LOGFILE"
  322.                 echo "Creating folder \"$DESTINATION\"" >> "$LOGFILE"
  323.                 mkdir "$DESTINATION"
  324.                 if [[ ! -d "$DESTINATION" ]]; then
  325.                     # Dialog 19: Folder creation error
  326.                     "$CD" msgbox --title "$STRING19a" --text "$STRING19b" --informative-text "$STRING19c" --style critical --button1 "$STRING19d" > /dev/null
  327.                     exit 1
  328.                 fi
  329.                 return;;
  330.     esac
  331.     echo "Creating disk image \"$DISKIMAGE\"" >> "$LOGFILE"
  332.     hdiutil create -quiet $ENCRYPTION -fs HFS+J -type SPARSE -size 60g -volname "$USER Backup" "$DISKIMAGE" >> "$LOGFILE"
  333.     if [[ ! -f "$DISKIMAGE" ]]; then
  334.         # Dialog 20: Disk image creation error
  335.         "$CD" msgbox --title "$STRING20a" --text "$STRING20b" --informative-text "$STRING20c" --style critical --button1 "$STRING20d" > /dev/null
  336.         exit 1
  337.     fi
  338. }
  339.  
  340. ### Mounts the disk image, if present, then checks the destination to see if
  341. ### ownership is enabled.
  342. prepare_destination() {
  343.     if [[ -d "$DESTINATION" && ! -e "$DISKIMAGE" ]]; then
  344.         echo "Not using a disk image" >> "$LOGFILE"
  345.     else
  346.         echo "Using disk image \"$DISKIMAGE\"" >> "$LOGFILE"
  347.         DESTINATION="/Volumes/$USER Backup"
  348.         DEVICE=$(hdiutil attach "$DISKIMAGE" | awk "\$0~/$USER Backup/ {print \$1}")
  349.         if ([[ ! -d "$DESTINATION" ]] || [[ -d "$DESTINATION 1" ]]); then
  350.             echo "Error mounting disk image.  Contents of /Volumes:" >> "$LOGFILE"
  351.             echo "$(ls -al /Volumes/)" >> "$LOGFILE"
  352.             # Dialog 21: Disk image mounting error
  353.             "$CD" msgbox --title "$STRING21a" --text "$STRING21b" --style critical --button1 "$STRING21c" > /dev/null
  354.             bug_report
  355.             exit 1
  356.         fi
  357.     fi
  358.     test_permissions "$DESTINATION"
  359.     ! (vsdbutil -c "$DESTINATION" | grep -q "enabled") && enable_ownership
  360. }
  361.  
  362. # Jump through hoops to properly pass UTF-16 disk name to AppleScript
  363. enable_ownership() {
  364.     echo "$DESTINATION" | awk -F/ '{print $3}' | iconv -f UTF-8 -t UTF-16 | tr -d '\n' > /tmp/iPodBackup-temp$$
  365.     osascript -e "set MYDISK to read POSIX file \"/tmp/iPodBackup-temp$$\" as Unicode text" -e "tell application \"Finder\" to set ignore privileges of disk MYDISK to false" > /dev/null 2>> "$LOGFILE"
  366.     if ([[ "$?" = "1" ]] && ([[ "$OWNERSHIP_WARNING" != "false" ]] && (vsdbutil -c "$DESTINATION" | grep -q "disabled"))); then
  367.         # Dialog 22: Ownership error
  368.         ASK=$("$CD" msgbox --title "$STRING22a" --text "$STRING22b" --informative-text "$STRING22c" --button1 "$STRING22d" --button2 "$STRING22e" --no-newline)
  369.         case "$ASK" in
  370.             "1")    ;;
  371.             "2")    set_pref ownership_warning false;;
  372.         esac
  373.     fi
  374. }
  375.  
  376. ### Calls rsync to do the dirty work, then makes sure the log doesn't explode.
  377. execute_backup() {
  378.     echo "Backing up \"$HOME\" to \"$DESTINATION\" using \"$RSYNC $ARGS\"" >> "$LOGFILE"
  379.     if (( "$OS_VERSION" <= "7" )); then
  380.         # Execute sync in background and monitor logfile for explosion
  381.         "$RSYNC" $ARGS --exclude-from "$EXCEPTIONS" "$HOME"/ "$DESTINATION" --delete >> "$LOGFILE" 2>> "$LOGFILE" &
  382.         sleep 5
  383.         while (ps -ww -p $! | grep -q "$RSYNC"); do
  384.             LOGSIZE=$(ls -l "$LOGFILE" | awk '{print $5}')
  385.             if (( "$LOGSIZE" > "$DANGER_LEVEL" )); then
  386.                 killall -u "$USER" -STOP rsync
  387.                 echo "Caught rsync misbehaving with log at $LOGSIZE bytes" >> "$LOGFILE"
  388.                 # Dialog 23: Exploding log warning
  389.                 ASK=$("$CD" msgbox --title "$STRING23a" --text "$STRING23b" --informative-text "$STRING23c" --style critical --button1 "$STRING23d" --button2 "$STRING23e" --no-newline)
  390.                 case "$ASK" in
  391.                     "1")    killall -u "$USER" -KILL rsync
  392.                             return 1;;
  393.                     "2")    echo "rsync given one more chance" >> "$LOGFILE"
  394.                             (( DANGER_LEVEL *= 2 ))
  395.                             killall -u "$USER" -CONT rsync;;
  396.                 esac
  397.             fi
  398.             sleep 5
  399.         done
  400.         # Scrape exit code from logfile ($? doesn't work for background procs)
  401.         EXIT_CODE=$(awk -F"code " '$0~/^rsync.*\(code/ {print $2}' "$LOGFILE" | awk -F")" '{print $1}' | head -n 1)
  402.         [[ -z "$EXIT_CODE" ]] && EXIT_CODE=0
  403.     else
  404.         "$RSYNC" $ARGS --exclude-from "$EXCEPTIONS" "$HOME"/ "$DESTINATION" --delete >> "$LOGFILE" 2>> "$LOGFILE"
  405.         EXIT_CODE="$?"
  406.     fi
  407.     return "$EXIT_CODE"
  408. }
  409.  
  410. ### Deals with failure on the part of execute_backup
  411. error_handling() {
  412.     case "$1" in
  413.         "0" | "23")
  414.             echo "Backup finished without major errors (exit code $1)" >> "$LOGFILE"
  415.             donation_nag;;
  416.         "24")
  417.             echo "Backup finished with minor errors (exit code $1)" >> "$LOGFILE"
  418.             # Dialog 29: IO error occurred
  419.             ASK=$("$CD" msgbox --title "$STRING29a" --text "$STRING29b" --informative-text "$STRING29c" --style critical --button1 "$STRING29d" --button2 "$STRING29e" --no-newline)
  420.             case "$ASK" in
  421.                 "1")    ;;
  422.                 "2")    set_pref ignore_errors true;;
  423.             esac
  424.             donation_nag;;
  425.         "12")
  426.             if ( (grep -q "No space left on device" "$LOGFILE") || (( "$OS_VERSION" <= "7" ))); then
  427.                 echo "Backup did not finish, possibly because destination is full (exit code $1)" >> "$LOGFILE"
  428.                 # Dialog 30: Destination full?
  429.                 ASK=$("$CD" msgbox --title "$STRING30a" --text "$STRING30b" --informative-text "$STRING30c" --style critical --button1 "$STRING30d" --no-newline)
  430.             elif (( "$OS_VERSION" >= "8" )); then
  431.                 echo "An error occurred: rsync exited with code $1" >> "$LOGFILE"
  432.                 tiger_bug_report
  433.             fi;;
  434.         "138")
  435.             if (( "$OS_VERSION" >= "8" )); then
  436.                 echo "An error occurred: rsync exited with code $1" >> "$LOGFILE"
  437.                 tiger_bug_report
  438.             else
  439.                 echo "An error occurred: rsync exited with code $1" >> "$LOGFILE"
  440.                 bug_report
  441.             fi;;
  442.         *)    echo "An error occurred: rsync exited with code $1" >> "$LOGFILE"
  443.             bug_report
  444.         esac
  445.     cp "$LOGFILE" "$DESTINATION"
  446. }
  447.  
  448. ### Prepares and offers to send a bug report
  449. bug_report() {
  450.     COMPACT=false
  451.     CHIME=false
  452.     if (( "$(awk 'END {print NR}' "$LOGFILE")" > "100" )); then
  453.         TEMP_LOG=/tmp/"$APPNAME Bug Report $$.log"
  454.         head -n 50 "$LOGFILE" > "$TEMP_LOG"
  455.         echo "(...)" >> "$TEMP_LOG"
  456.         tail -n 50 "$LOGFILE" >> "$TEMP_LOG"
  457.     else
  458.         TEMP_LOG="$LOGFILE"
  459.     fi
  460.     open "$TEMP_LOG"
  461.     # Dialog 24: Send bug report
  462.     ASK=$("$CD" msgbox --title "$STRING24a" --text "$STRING24b" --informative-text "$STRING24c" --style critical --button1 "$STRING24d" --button2 "$STRING24e" --no-newline)
  463.     case "$ASK" in
  464.         "1")    ;;
  465.         "2")    SUBJECT="$APPNAME $VERSION ($DATE) bug report"
  466.                 osascript -e "tell application \"Mail\"" \
  467.                     -e "set NEW_MESSAGE to make new outgoing message with properties {subject:\"$SUBJECT\", content:\"Bug report\n\"}" \
  468.                     -e "tell NEW_MESSAGE" \
  469.                     -e "set visible to true" \
  470.                     -e "make new to recipient at beginning of to recipients with properties {name:\"$EMAIL\", address:\"$EMAIL\"}" \
  471.                     -e "tell content" \
  472.                     -e "make new attachment with properties {file name:\"$TEMP_LOG\"} at after the last word of the last paragraph" \
  473.                     -e "end tell" \
  474.                     -e "end tell" \
  475.                     -e "end tell";;
  476.     esac
  477. }
  478.  
  479. ### Bitches about Tiger rsync bugs and then calls bug_report
  480. tiger_bug_report() {
  481.     # Dialog 27: Tiger rsync bug
  482.     ASK=$("$CD" msgbox --title "$STRING27a" --text "$STRING27b" --informative-text "$STRING27c" --style critical --button1 "$STRING27d" --button2 "$STRING27e" --no-newline)
  483.     case "$ASK" in
  484.         "1")    ;;
  485.         "2")    set_pref manual_os_version 7;;
  486.     esac
  487.     bug_report
  488. }
  489.  
  490. ### Unmounts the disk image if one was used.  Also compacts, if enabled.
  491. unmount_diskimage() {
  492.     if [[ -d "/Volumes/$USER Backup" ]]; then
  493.         hdiutil detach -quiet "$DEVICE"
  494.         echo "Disk image ejected" >> "$LOGFILE"
  495.         # Compact or not?
  496.         if [[ "$COMPACT" = "true" ]]; then
  497.             echo -n "Compacting disk image ... " >> "$LOGFILE"
  498.             hdiutil compact "$DISKIMAGE" -quiet >> "$LOGFILE"
  499.             echo "done" >> "$LOGFILE"
  500.         fi
  501.     fi
  502. }
  503.  
  504. ### Plays chime, if enabled.
  505. postflight_chime() {
  506.     if [[ "$CHIME" != "none" && -f "$CHIME" ]]; then
  507.         echo "Playing chime \"$CHIME\"" >> "$LOGFILE"
  508.         "$THISAPP"/sndplay "$CHIME"
  509.     fi
  510. }
  511.  
  512. ### Sends alert to Growl
  513. send_growl_notification() {
  514.     if (ps -axww | grep "GrowlHelperApp" | grep -qv "grep"); then
  515.         "$THISAPP"/growlnotify -n "$APPNAME" -a "$APPNAME" "$APPNAME" -m "$1"
  516.     fi
  517. }
  518.  
  519. ### Nags about giving a donation.  What can I say?  I'm poor.
  520. donation_nag() {
  521.     DATA_SIZE=$(awk '($1=="sent" || $1=="wrote") && $3=="bytes" {print $2}' "$LOGFILE")
  522.     [[ ! -z "$DATA_SIZE" ]] && (( TOTAL_DATA_SIZE+="$DATA_SIZE" ))
  523.     (( TOTAL_RUNS++ ))
  524.     set_pref total_data_size "$TOTAL_DATA_SIZE"
  525.     set_pref total_runs "$TOTAL_RUNS"
  526.     if ([[ "$DO_NOT_NAG" != "true" ]] && (echo "$TOTAL_RUNS/10" | bc -l | grep -q "\.0*$")); then
  527.         # Dialog 25: Donation nag
  528.         ASK=$("$CD" msgbox --title "$STRING25a" --text "$STRING25b" --informative-text "$STRING25c $TOTAL_RUNS $STRING25d $(format_datasize $TOTAL_DATA_SIZE) $STRING25e" --button1 "$STRING25f" --button2 "$STRING25g" --button3 "$STRING25h" --no-newline)
  529.         case "$ASK" in
  530.             "1")    ;;
  531.             "2")    open "http://s1.amazon.com/paypage/PCIAODJIUOEET";;
  532.             "3")    # Dialog 26: Thanks for donation
  533.                     "$CD" msgbox --title "$STRING26a" --text "$STRING26b" --button1 "$STRING26c" > /dev/null
  534.                     set_pref do_not_nag true;;
  535.         esac
  536.     fi
  537. }
  538.  
  539. ### Formats datasize to be pretty
  540. format_datasize() {
  541.     if (( "$1" < 1024 )); then
  542.         RESULT="$1 B"
  543.     elif (( "$1" < 1024 * 1024 )); then
  544.         RESULT="$(( $1 / 1024 )) KB"
  545.     elif (( "$1" < 1024 * 1024 * 1024 )); then
  546.         RESULT="$(( $1 / 1024 / 1024 )) MB"
  547.     else
  548.         RESULT="$(( $1 / 1024 / 1024 / 1024 )) GB"
  549.     fi
  550.     echo "$RESULT"
  551. }
  552.  
  553. ### Checks to see if a new version of iPodBackup is available
  554. update_check() {
  555.     if ([[ "$CHECK_FOR_UPDATES" != "false" ]] && (ping -c 1 -t 3 www.google.com | grep -q "1 packets received")); then
  556.         NEWEST_VERSION=($(curl -Ls "$HOMEPAGE/software/$(echo $APPNAME | tr "[:upper:]" "[:lower:]")/properties.php?print"))
  557.         if ([[ ! -z "$NEWEST_VERSION" ]] && ([[ "$(get_newest ${NEWEST_VERSION[0]} $VERSION ".")" = "1" ]] || [[ "$(get_newest ${NEWEST_VERSION[1]} $DATE "-")" = "1" ]]) ); then
  558.             echo "Update check:  Newer version is available: ${NEWEST_VERSION[0]} (${NEWEST_VERSION[1]})" >> "$LOGFILE"
  559.             # Dialog 28: New version available
  560.             ASK=$("$CD" msgbox --title "$STRING28a" --text "$STRING28b" --informative-text "$STRING28c$VERSION ($DATE)$STRING28d${NEWEST_VERSION[0]} (${NEWEST_VERSION[1]})$STRING28e" --button1 "$STRING28f" --button2 "$STRING28g" --button3 "$STRING28h" --no-newline)
  561.             case "$ASK" in
  562.                 "1")    open $HOMEPAGE/software/?download=$APPNAME;;
  563.                 "2")    ;;
  564.                 "3")    set_pref check_for_updates false;;
  565.             esac
  566.         else
  567.             echo "Update check:  Current version is up-to-date" >> "$LOGFILE"
  568.         fi
  569.     fi
  570. }
  571.  
  572. ### Compares two strings (either a date or a version) to see which is newer.
  573. ### Must specify the type of string as third argument.
  574. ### Returns 1 if $1 is newer, 2 for $2, or 0 if both are same
  575. get_newest() {
  576.     if [[ "$#" != "3" ]]; then
  577.         echo "Error attempting to compare \"$1\" and \"$2\": Bad arguments" >> "$LOGFILE"
  578.         exit 1
  579.     fi
  580.     FS=$3
  581.     for A in 1 2 3; do
  582.         NUM1[$A]=$(echo "$1" | awk -F$FS "{print \$$A}" | sed -e "s/^0//")
  583.         NUM2[$A]=$(echo "$2" | awk -F$FS "{print \$$A}" | sed -e "s/^0//")
  584.         if (( "${NUM1[$A]}" > "${NUM2[$A]}" )); then
  585.             echo "1"
  586.             return
  587.         elif (( "${NUM1[$A]}" < "${NUM2[$A]}" )); then
  588.             echo "2"
  589.             return
  590.         fi
  591.     done
  592.     echo "0"
  593. }
  594.  
  595. ### The real deal.  This is where the code is actually executed.
  596. mkdir -m 700 "$HOME"/Library/Logs 2> /dev/null
  597. echo "$APPNAME version $VERSION ($DATE) running on $(uname -sr)" > "$LOGFILE"
  598. echo "Please contact $EMAIL if you have any problems" >> "$LOGFILE"
  599. date >> "$LOGFILE"
  600. get_language
  601. load_preferences
  602. test_permissions "$LOGFILE"
  603. handle_arguments "$@"
  604. test_for_ipod
  605. if [[ ! -d "$DESTINATION" && ! -f "$DISKIMAGE"  ]]; then
  606.     first_time_setup
  607.     load_preferences
  608. fi
  609. preflight_prompt
  610. send_growl_notification "$STRING31a $IPODNAME $STRING31b"
  611. prepare_destination
  612. execute_backup
  613. error_handling $?
  614. unmount_diskimage
  615. postflight_chime
  616. send_growl_notification "$STRING32a"
  617. update_check
  618. echo "$APPNAME quit at "$(date) >> "$LOGFILE"
  619.